package maker;

import java.util.ArrayList;
import java.util.concurrent.LinkedBlockingQueue; // for channels
import java.io.BufferedReader;
import java.io.FileReader;
import java.math.BigInteger;
import java.util.Iterator;
import java.io.InputStreamReader;
import java.util.StringTokenizer;

public class ISASimulator {

     public static final int D_MEM_SIZE = 8192;
     public static final int I_MEM_SIZE = 8192;
     public static final int NUM_CHANNELS = 4;

     // constant values that depends on your ISA
     // TODO: modify this
     public static final int REG_FILE_SIZE = 32;
     public static final int OPCODE_LENGTH = 5;
     
     // standard instructions
     public static final int RS_LENGTH = 4;
     public static final int RD_LENGTH = 4;
     public static final int RT_LENGTH = 4;
     
     // immediate functions
     public static final int REG_IMMED = 12;
     
     // special instruction: load immediate store
     public static final int LDS_RD = 5;
     public static final int LDS_IMMED = 7;
     
     // special instruction: set
     public static final int RD_LEN_ST = 5;
     public static final int RS_LEN_ST = 5;
     public static final int RT_LEN_ST = 2;
     
     // special instruction: add
     public static final int RD_LEN_AD = 3;
     public static final int RS_LEN_AD = 4;
     public static final int RT_LEN_AD = 5;
     

     private int channel_buffer_size;

     private int PC; // current program counter
     private int inst_count; // number of instructions we have executed for the simulator

     private String[] inst_mem; // inst_mem is kept in a string for easier parsing of opcode/operands/etc
     private Int34[] data_mem;

     private Int34[] reg_file;

     private ArrayList<LinkedBlockingQueue<Int34>> channels; //Array  of Linked Lists (LL) which each list acting as a channel. 

     
	 // array of registers. used to make debugging statements much clearer
	 String[] regis = {"$0", "$1", "$x", "$y", "$z", "$sp", "$ret0", "$param0",
				"$param1", "$a", "$b", "$save", "$load", "$cmp1", "$cmp2", "$ret_add", 
				"$e1", "$e2", "$e3", "$e4", "$e5", "$e6", "$e7", "$e8",
				"$e9", "$5", "$10"};
     
     // default constructor
     public ISASimulator() { 
	  // init PC to 0
	  PC = 0;

	  channel_buffer_size = 16; // set default channel buffer size

	  // initialize memories
	  inst_mem = new String[I_MEM_SIZE];
	  data_mem = new Int34[D_MEM_SIZE];

	  initMem(true);
	  initMem(false);

	  // initialize register file
	  reg_file = new Int34[REG_FILE_SIZE];

	  initRegFile(); // clear registers

	  // init channels
	  channels = new ArrayList<LinkedBlockingQueue<Int34>>(NUM_CHANNELS);
	  for(int i = 0; i < NUM_CHANNELS; ++i)
	       channels.add(new LinkedBlockingQueue<Int34>());

	  //clearChannels();
     }

     // reset everythinig to it's initial state (i.e. memory/registers/channels cleared and PC = 0)
     public void resetSimulator() {
	  PC = 0;

	  // reset memory
	  clearMem(true);
	  clearMem(false);

	  // reset register file
	  clearRegFile();

	  // make sure we have the default channel size
	  channel_buffer_size = 16;
	  setBufferSize(channel_buffer_size); // this also clears the channels
     }

     // initialize the memory (create and zero out)
     public void initMem(boolean imem) {
	  if(imem) { for(int i = 0; i < I_MEM_SIZE; ++i) inst_mem[i] = new String("00000000000000000"); }
	  else { for(int i = 0; i < D_MEM_SIZE; ++i) data_mem[i] = new Int34(0L); }
     }

     // clears the memory: imem = true means inst memory, false = data mem
     public void clearMem(boolean imem) {
	  if(imem) {
	       for(int i = 0; i < I_MEM_SIZE; ++i) inst_mem[i] = "00000000000000000";
	  }
	  else {
	       for(int i = 0; i < D_MEM_SIZE; ++i) data_mem[i].create(0,0L);
	  }
     }

     // init the reg files to all 0's
     public void initRegFile() { for(int i = 0; i < REG_FILE_SIZE; ++i) reg_file[i] = new Int34(0L); }

     // clears the register file
     public void clearRegFile() {
	  for(int i = 0; i < REG_FILE_SIZE; ++i)
	       reg_file[i].create(0,0L);
     }

     // clears all channels
     public void clearChannels() {
	  for(int i = 0; i < NUM_CHANNELS; ++i)
	       clearChannel(i);
     }

     // clear a specific channel (chan_num)
     public void clearChannel(int chan_num) {
	  // sanity check on input
	  if(chan_num < 0 || chan_num >= NUM_CHANNELS) {
	       System.out.println("Invalid channel number: " + chan_num);
	       return;
	  }

	  channels.get(chan_num).clear();
     }

     /*
      * Description: loads either imem (imem = true) or dmem with a COE file (starting at address "start_addr")
      *
      * Returns: true if successful, false otherwise
      *
      * Note: dmem expects the input to be in 2's complement format... this is a bit hackish as sometimes the memory location will
      * be a pointer and therefore not really be 2's complement. However, we can (reasonably) assume that this won't be a
      * problem because the addresses used will never be large enough to have a MSB of 1 (since the memory is only 8K)
      */
     public boolean loadMem(String coe, int start_addr, boolean imem) {
	  int curr_addr = start_addr; // which address we are currently pointing to
	  int curr_line = -1; // line number of the file that is currently read in
	  String line, tmp;
	 
	  
	  int radix = 16; // radix being used... default is 16

	  BufferedReader in = null;

	  // load COE file
	  try { in = new BufferedReader(new FileReader(coe)); }
	  catch(Exception e) { 
	       System.out.println("Couldn't open file: " + coe + ". Load failed."); 
	       return false;
	  }

	  // read in first 2 lines of COE, which have a specific format
	  try {
	       line = in.readLine(); // this line should be MEMORY_INITIALIZATION_RADIX=XXX
	       System.out.println(line);
	       curr_line++;
	       if(!line.contains("MEMORY_INITIALIZATION_RADIX=")) {
		    System.out.println("Expected MEMORY_INITIALIZATION_RADIX=...; on line 0 of file. Load failed.");
		    return false;
	       }

	       // extract the radix value from the string
	       String radix_string = line.split("=")[1];
	       radix_string = radix_string.substring(0,radix_string.length()-1); // strip off the trailing ";"
	       radix = Integer.valueOf(radix_string).intValue(); // convert to int

	       // only support binary and hex for now
	       if(radix != 2 && radix != 16) {
		    System.out.println("Radix format must be 2 (binary) or 16 (decimal). Load failed.");
		    return false;
	       }

	       line = in.readLine(); // this line should be MEMORY_INITIALIZATION_VECTOR=
	       curr_line++;
	       if(!line.contentEquals("MEMORY_INITIALIZATION_VECTOR=")) {
		    System.out.println("line 1 must be \"MEMORY_INITIALIZATION_VECTOR=\". Load failed.");
		    return false;
	       }
	  }
	  catch(Exception e) {
	       System.out.println("Error reading input file. Load failed.");
	       return false;
	  }
		
	  // loop through file and get all the info
	  try {
	       line = in.readLine();
	       curr_line++;

	       while(line != null) { // stop when we read EOF
		    if((imem && curr_addr >= I_MEM_SIZE) || (!imem && curr_addr >= D_MEM_SIZE)) {
			 System.out.println("Too many addresses specified in COE file. Load failed.");
			 return false;
		    }

		    // if imem then we simply set the mem location as the binary version of the string (minus the trailing ',')
		    // if radix isn't binary, then conversion to binary must take place here must take place here
		    tmp = "";
		    if(imem) {
			 // strip off trailing ',' if there is one (should be EOF soon if not one)
			 if(line.substring(line.length()-1,line.length()).equals(","))
			      line = line.substring(0,line.length()-1);

			 // make sure the string is the correct length (based on radix)
			 if((radix == 2 && line.length() != 17) || (radix == 16 && line.length() != 5)) {
			      System.out.println(line + " (line " + curr_line + ") has incorrect format. Load failed.");
			      return false;
			 }

			 // radix 2 means we just copy the string over to the imem location
			 if(radix == 2) {
			      inst_mem[curr_addr] = line;
			      curr_addr++;
			 }
			 else { // need to convert each digit to binary string first
			      // since our instruction length is not a multiple of 4, the first hex digit must be 0 or 1
			      if(line.charAt(0) != '0' && line.charAt(0) != '1') {
				   System.out.println(line + " (line " + curr_line + "): First hex value must be 0 or 1. Load failed.");
				   return false;
			      }
			      else { // tack on first value
				   if(line.charAt(0) == '0') tmp = "0";
				   else tmp = "1";
			      }

			      String hexString = "";
			      // loop through and convert all the values
			      for(int i = 1; i < line.length(); ++i) {
				   hexString = hexToBin(line.charAt(i));
				   if(hexString.length() == 0) { // make sure the value was 0 - f and not some random char
					System.out.println(line + " (line " + curr_line + "): Non-Hex value encountered. Load failed.");
					return false;
				   }
				   tmp += hexString;
			      }

			      inst_mem[curr_addr] = tmp;
			      curr_addr++;
			 }
		    }

		    else { // convert to Int34 before loading into dmem
			 boolean isPositive = true; // is this a positive value?

			 line = line.substring(0,line.length()-1); // strip off the trailing ','

			 // make sure the string is the correct length (based on radix)
			 if((radix == 2 && line.length() != 34) || (radix == 16 && line.length() != 9)) {
			      System.out.println(line + " (line " + curr_line + ") has incorrect format. Load failed.");
			      return false;
			 }

			 // radix 2 means we just copy the string over to the imem location
			 if(radix == 2)
			      tmp = line;

			 else { // need to convert each digit to binary string first
			      // manually convert the first digit
			      // if first binary digit is 0, then it is a positive number, else it is negative
			      if(line.charAt(0) == '0') { tmp = "00"; isPositive = true; }
			      else if(line.charAt(0) == '1') { tmp = "01"; isPositive = true; }
			      else if(line.charAt(0) == '2') { tmp = "10"; isPositive = false; }
			      else if(line.charAt(0) == '3') { tmp = "11"; isPositive = false; }
			      else {
				   System.out.println(line + " (line " + curr_line + "): First hex value must be 0, 1, 2, or 3. Load failed.");
				   return false;
			      }

			      String hexString = "";
			      // loop through and convert all the values
			      for(int i = 1; i < line.length(); ++i) {
				   hexString = hexToBin(line.charAt(i));
				   if(hexString.length() == 0) { // make sure the value was 0 - f and not some random char
					System.out.println(line + " (line " + curr_line + "): Non-Hex value encountered. Load failed.");
					return false;
				   }
				   tmp += hexString;
			      }

			 }

			 Int34 line_value;

			 // if the number was positive, we can just convert string directly to big integer
			 if(isPositive) {
			      BigInteger thisNum = new BigInteger(tmp,2);
			      line_value = new Int34(thisNum.signum(),thisNum.longValue());
			 }
			 // if negative, we first need to do twos complement
			 else {
			      boolean flip = false;
			      String twos_comp = "";
			      // leave all LSBs up to the first '1' unchanged, then flip them all
			      for(int i = tmp.length()-1; i >= 0; --i) {
				   if(!flip) {
					twos_comp = tmp.charAt(i) + twos_comp;
					if(tmp.charAt(i) == '1') flip = true; // start flipping from now on
				   }
				   else if(tmp.charAt(i) == '0') twos_comp = '1' + twos_comp;
				   else twos_comp = '0' + twos_comp;
			      }

			      // tack on a negative sign
			      twos_comp = "-" + twos_comp;

			      //BigInteger thisNum = new BigInteger(twos_comp,2);
			      //line_value = new Int34(thisNum.signum(),thisNum.longValue());
			      line_value = new Int34(Long.parseLong(twos_comp,2));
			 }

			 data_mem[curr_addr] = line_value;
			 curr_addr++;
		    }

		    line = in.readLine(); // read in next line
		    curr_line++;
	       } // end while

	       in.close();
	  }
	  catch(Exception e) {
	       System.out.println("Error reading input file. Load failed.");
	       return false;
	  }

	  return true;
     }

     // given a character (0 - f) returns a string of the binary representation of that num
     public String hexToBin(char hex_char) {
	  String result;
	  switch(Character.digit(hex_char,16)) {
	  case 0:
	       result = "0000";
	       break;
	  case 1:
	       result = "0001";
	       break;
	  case 2:
	       result = "0010";
	       break;
	  case 3:
	       result = "0011";
	       break;
	  case 4:
	       result = "0100";
	       break;
	  case 5:
	       result = "0101";
	       break;
	  case 6:
	       result = "0110";
	       break;
	  case 7:
	       result = "0111";
	       break;
	  case 8:
	       result = "1000";
	       break;
	  case 9:
	       result = "1001";
	       break;
	  case 10:
	       result = "1010";
	       break;
	  case 11:
	       result = "1011";
	       break;
	  case 12:
	       result = "1100";
	       break;
	  case 13:
	       result = "1101";
	       break;
	  case 14:
	       result = "1110";
	       break;
	  case 15:
	       result = "1111";
	       break;
	  default:
	       result = "";
	       break;
	  }
		
	  return result;
     }

     public void printIMem() { printIMem(0, I_MEM_SIZE-1); }
     public void printDMem() { printIMem(0, D_MEM_SIZE-1); }

     // disassembles instructions starting at start_addr and going to start_addr+range
     public void printIMem(int start_addr, int range) {
	  // sanity check of inputs
	  if(range < 0) {
	       System.out.println("Range must be positive.");
	       return;
	  }
	  if(start_addr + range >= I_MEM_SIZE) {
	       System.out.println("startaddr + size must be less than " + I_MEM_SIZE);
	       return;
	  }

	  // loop through and print values
	  for(int i = start_addr; i < start_addr + range; ++i)
	       System.out.println("IMEM[" + i + "]: " + inst_mem[i]);
     }

     // prints data mem contents from start_addr to start_addr+range
     public void printDMem(int start_addr, int range) {
	  // sanity check of inputs
	  if(range < 0) {
	       System.out.println("Range must be non-negative.");
	       return;
	  }
	  if(start_addr + range >= D_MEM_SIZE) {
	       System.out.println("startaddr + size must be less than " + D_MEM_SIZE);
	       return;
	  }

	  // loop through and print values
	  for(int i = start_addr; i <= start_addr + range; ++i)
	       System.out.println("DMEM[" + i + "]: " + data_mem[i]);
     }

     // prints the contents of all the registers
     public void printRegFile() {
	  for(int i = 0; i < REG_FILE_SIZE; ++i)
	       System.out.println(i + ": " + reg_file[i]);
     }

     // print out a specific channel
     public void printChannel(int chan_num) {
	  // sanity check on input
	  if(chan_num < 0 || chan_num >= NUM_CHANNELS) {
	       System.out.println("Invalid channel number: " + chan_num);
	       return;
	  }

	  Iterator<Int34> chan_vals = channels.get(chan_num).iterator(); // get an iterator for this buffer

	  int index = 0;

	  while(chan_vals.hasNext()) {
	       System.out.println(index + ": " + chan_vals.next());
	       index++;
	  }
     }

     // set specific register to a specific value
     public void setReg(int reg_num, Int34 value) {
	  // sanity check of input
	  if(reg_num < 0 || reg_num >= REG_FILE_SIZE) {
	       System.out.println("Invalid register number: " + reg_num);
	       return;
	  }

	  reg_file[reg_num] = value;
     }

     // set specific imem location (addr) to a specific value
     public void setIMem(int addr, String value) {
	  // sanity check of input
	  if(addr < 0 || addr >= I_MEM_SIZE) {
	       System.out.println("Address out of range: " + addr);
	       return;
	  }

	  if(!isIMemFormat(value)) {
	       System.out.println("Incorrect format: " + value);
	       return;
	  }

	  inst_mem[addr] = value;
     }

     // makes sure that this is a 17 bit binary number in string format
     public boolean isIMemFormat(String value) {
	  if(value.length() != 17) return false; // not 17 bits
		
	  // see if all chars are 0 or 1
	  for(int i = 0; i < 17; ++i) {
	       if(value.charAt(i) != '0' && value.charAt(i) != '1') return false;
	  }

	  return true;
     }

     // set specific dmem location (addr) to a specific value
     public void setDMem(int addr, Int34 value) {
	  // sanity check of input
	  if(addr < 0 || addr >= D_MEM_SIZE) {
	       System.out.println("Address out of range: " + addr);
	       return;
	  }

	  data_mem[addr] = value;
     }

     // adds a specific value to buffer of specified channel (chan_num)
     // returns true if added successfully, false otherwise
     public boolean addToChannel(int chan_num, Int34 value) {
	  // sanity check of input
	  if(chan_num < 0 || chan_num >= NUM_CHANNELS) {
	       System.out.println("Invalid channel number: " + chan_num);
	       return false;
	  }

	  return channels.get(chan_num).offer(value); // returns false if no room in the buffer
     }

     // resizes the channel buffers. also clears channels (so make sure there isn't anything important there)
     public void setBufferSize(int size) {
	  if(size < 0) {
	       System.out.println("buffer size must be non-negative");
	       return;
	  }

	  channel_buffer_size = size;

	  for(int i = 0; i < NUM_CHANNELS; ++i)
	       channels.get(i).clear();
     }

     // sign extend a string to a given length (assumes (i.e. doesn't check) it is a string of 0's and 1's)
     public String signExtend(String value, int length) {
	  String sign_bit = value.substring(0,1);

	  while(value.length() < length) value = sign_bit + value;

	  return value;
     }

     // converts binary string into an Int34
     // assumes input value is strictly a binary number (doesn't check it)
     public Int34 twosCompValue(String value) {
	  // examine first bit to see if it's positive or negative
	  if(value.charAt(0) == '0') { // positive number
	       return new Int34(Long.parseLong(value,2));
	  }
	  else { // negative number

	       boolean flip = false;
	       String twos_comp = "";
	       // leave all LSBs up to the first '1' unchanged, then flip them all
	       for(int i = value.length()-1; i >= 0; --i) {
		    if(!flip) {
			 twos_comp = value.charAt(i) + twos_comp;
			 if(value.charAt(i) == '1') flip = true; // start flipping from now on
		    }
		    else if(value.charAt(i) == '0') twos_comp = '1' + twos_comp;
		    else twos_comp = '0' + twos_comp;
	       }

	       // tack on a negative sign
	       twos_comp = "-" + twos_comp;

	       return new Int34(Long.parseLong(twos_comp,2));
	  }
     }

     // execute num_insts instruction
     public void execute(int num_insts) {
	  // sanity check of inputs
	  if(num_insts < 1) {
	       System.out.println("Number of instructions must be positive.");
	       return;
	  }

	  int num_done = 0; // number of instructions we have completed so far
	  String curr_inst; // the current instruction
	  String opcode_str; // string representing the opcode
	  int opcode;  // the opcode in integer form (so we can use a case statement)
	  int rs, rt, rd, imm, set_rd, set_rs, set_rt, sp_rd, sp_rs, sp_rt, lds_rd, imm_7bit, j, address;
	  Int34 rs34, rt34, rd34, imm34, set_rd34, set_rs34, set_rt34, sp_rd34, sp_rs34, sp_rt34, lds_rd34, imm_7bit34;
	  String s;
	 
	  
	  // compare operations compare compare 1 w/ compare 2
	  int compare1 = 13;			
	  int compare2 = 14;
	  
	  int save = 11;			// used in preset register save operations
	  int load = 12; 			// used in preset register load operations
	  int return_address = 15;
	  int sp = 5;
	  
	  
	  // condition is wrong??? maybe change to while(1) and let halt exit it
	  while(num_done < num_insts) {
	       curr_inst = inst_mem[PC]; // get the next instruction
	       //System.out.println(curr_inst);

	       //opcode_str = curr_inst.substring(0,OPCODE_LENGTH); // get the op-code bits
	       //opcode = Integer.valueOf(opcode_str,2).intValue();
	    
	       System.out.println(curr_inst);
	       
	       // read the opcode bits
	       opcode = setRegisters(curr_inst, 0, OPCODE_LENGTH);
	       
	       // for standard instruction use
	       
	       rd = setRegisters(curr_inst, 5, 9);
    	   rs = setRegisters(curr_inst, 9, 13);    	   
    	   rt = setRegisters(curr_inst, 13, 17);	   
    	   imm = setRegisters(curr_inst, 5, 17);
    	   set_rd = setRegisters(curr_inst, 5, 10);
    	   set_rs = setRegisters(curr_inst, 10, 15);
    	   set_rt = setRegisters(curr_inst, 15, 17);
    	   sp_rd = setRegisters(curr_inst, 5, 8);
    	   sp_rs = setRegisters(curr_inst, 8, 12);
    	   sp_rt = setRegisters(curr_inst, 12, 17);
    	   lds_rd = setRegisters(curr_inst, 5, 10);
    	   imm_7bit = setRegisters(curr_inst, 10, 17);
    	       	   
    	   rs34 = new Int34(rs);
    	   rt34 = new Int34(rt);
    	   rd34 = new Int34(rd);
    	   imm34 = new Int34(imm);
    	   set_rd34 = new Int34(set_rd);
    	   set_rs34 = new Int34(set_rs);
    	   set_rt34 = new Int34(set_rt);
    	   sp_rd34 = new Int34(sp_rd);
    	   sp_rs34 = new Int34(sp_rs);
    	   sp_rt34 = new Int34(sp_rt);
    	   lds_rd34 = new Int34(lds_rd);
    	   imm_7bit34 = new Int34(imm_7bit);
    	   
//////////////////////////// Important! /////////////////////////    
    	  // Do we have to divide our addresses by 4? //
/////////////////////////////////////////////////////////////////
    	   
	       // TODO: complete this
	       switch (opcode) {
	       case 0: // add
	    	   //rd = rs + rt;
	    	   System.out.println(reg_file[rs].toString() + " + " + reg_file[rt].toString() + " = " + 
	    			   reg_file[rs].add(reg_file[rt]).longValue());
	    	   reg_file[rd] = reg_file[rs].add(reg_file[rt]);
		    break;
	       case 1: // sub
	    	   //rd = rs - rt;
	    	   System.out.println(reg_file[rs].toString() + " - " + reg_file[rt].toString() + " = " + 
	    			   reg_file[rs].subtract(reg_file[rt]).longValue());
	    	   reg_file[rd] = reg_file[rs].subtract(reg_file[rt]);
		    break;	    
	       case 2: // mul
	    	   System.out.println(reg_file[rs].toString() + " * " + reg_file[rt].toString() + " = " + 
	    			   new Int34(reg_file[rs].longValue() * reg_file[rt].longValue()));
	    	   setReg(rd, new Int34(reg_file[rs].longValue() *
	    			   reg_file[rt].longValue()));
		    break;
	       case 3: // or
	    	   reg_file[rd] = reg_file[rs].or(reg_file[rt]);
		    break;
	       case 4: // not
	    	   reg_file[rd] = reg_file[rs].not();
		    break;
	       case 5: // and
	    	   reg_file[rd] = reg_file[rs].and(reg_file[rt]);
		    break;
	       case 6: // srl
	    	   reg_file[rd] = reg_file[rs].shiftRight((int) reg_file[rt].longValue());
		    break;
	       case 7: // sll
	    	   reg_file[rd] = reg_file[rs].shiftLeft((int) reg_file[rt].longValue());
		    break;
	       case 8: // lw	    	   
	    	   address = (int) (reg_file[rs].add(reg_file[rt])).longValue(); // address
	    	   System.out.println("loading from address " + address + "(val: " + data_mem[address] +
	    			   ") into " + regis[rd]);
	    	   reg_file[rd] = data_mem[address];    	   
		    break;
	       case 9: // sw
	    	   address = (int) (reg_file[rs].add(reg_file[rt])).longValue(); // address
	    	   System.out.println("saving " + regis[rd] + "(val: " + reg_file[rd].longValue() + 
	    			   ") into address " + address);
	    	   data_mem[address] = reg_file[rd];
		    break;
	       case 10: // jr
	    	   System.out.println("returning to " + reg_file[set_rd].longValue() + " in " + regis[rd]);
	    	   PC = (int) reg_file[set_rd].longValue();
		    break;
	       case 11: // halt
	    	   System.out.println("ending program");
	    	   return;
// In/ Out don't work :'(
	       case 12: // in
	    	   reg_file[rd] = channels.get(reg_file[rt].bigValue.intValue()).element();
		    break;
	       case 13: // out
	    	   addToChannel(reg_file[rd].bigValue.intValue(), reg_file[rs]);
		    break;
	       case 14: // ldi
	    	   reg_file[load] = imm34;   
		    break;
	       case 15: // bgt
	    	   System.out.println("is " + reg_file[compare1].longValue() + " greater than " 
	    			   + reg_file[compare2].longValue() + "?");
	    	   if (reg_file[compare1].longValue() > reg_file[compare2].longValue())
	    	   { 
	    		   System.out.println("branching to " + imm + " cuz greater");
	    		   PC = imm; }
	    	   else { System.out.println("not branching to " + imm + " cuz not greater"); }
		    break;
	       case 16: // beq
	    	   System.out.println("is " + reg_file[compare1].longValue() + " equal to " + 
	    			   reg_file[compare2].longValue() + "?");
	    	   if (reg_file[compare1].equals(reg_file[compare2]))
	    	   { 
	    		   System.out.println("branching to " + imm + " cuz equals");
	    		   PC = imm; }
	    	   else { System.out.println("not branching to " + imm + " cuz not equals"); };
		    break;
	       case 17: // bgz
	    	   System.out.println("is " + reg_file[compare1].longValue() + " greater than 0?");
	    	   if (reg_file[compare1].longValue() > reg_file[0].longValue())
	    	   { 
	    		   System.out.println("branching to " + imm + " cuz greater than 0");
	    		   PC = imm; }
	    	   else { System.out.println("not branching to " + imm + " cuz not greater than 0"); }
		    break;
	       case 18: // jal
	    	   // do something else?
	    	   System.out.println("jumping to " + imm);
	    	   reg_file[return_address] = new Int34(PC);
	    	   PC = imm;
		    break;
	       case 19: // j_label
	    	   System.out.println("jumping to " + imm);
	    	   PC = imm;
		    break;
		    // load return address
	       case 20: // context load
	    	   reg_file[sp] = reg_file[sp].subtract(reg_file[1]);
	    	   reg_file[return_address] = data_mem[(int)reg_file[sp].longValue()];
	    	   System.out.println("loading context " + reg_file[return_address] + " from address " + 
	    			   (int)reg_file[sp].longValue());
	    	   
		    break;
		    // store return address
	       case 21: // context save
	    	   data_mem[(int)reg_file[sp].longValue()] = reg_file[return_address];
	    	   System.out.println("saving context " + reg_file[return_address] + 
	    			   " into address " + (int)reg_file[sp].longValue());
	    	   reg_file[sp] = reg_file[sp].add(reg_file[1]);
		    break;
	       case 22: // load label
	    	   s = "" + imm34.longValue();
	    	   j = Integer.valueOf(s, 2) ; 
	    	   reg_file[load] = data_mem[j];
	    	   System.out.println("storing " + data_mem[j] + " into " + regis[load]);
	    	   System.out.println("looked in address " + j);

	    	   
		    break;
	       case 23: // store label
	    	   s = "" + imm34.longValue();
	    	   j = Integer.valueOf(s, 2) ; 
	    	   data_mem[j] = reg_file[save];
		    break;
	       case 24: // set
	    	   System.out.println("setting " + regis[set_rd] + " to " + regis[set_rs]);
	    	   reg_file[set_rd] = reg_file[set_rs];
		    break;
	       case 25: // special add
	    	   System.out.println("adding " + reg_file[sp_rs].toString() + " to " + reg_file[sp_rt].toString());
	    	   reg_file[sp_rd] = reg_file[sp_rs].add(reg_file[sp_rt]);
		    break;
	       case 26: // load small immediate
	    	   System.out.println("loading " + imm_7bit + " into " + regis[lds_rd]);
	    	   reg_file[lds_rd] = imm_7bit34;
		    break;
	       case 27: // reserved
		    break;
	       case 28: // reserved
		    break;
	       case 29: // reserved
		    break;
	       case 30: // reserved
		    break;
	       case 31: // reserved
		    break;
	       case 32: // reserved
		    break;
	       default:
		    System.err.println("invalid opcode encountered at PC=" + PC);
		    return;
	       }
			
	       inst_count++; // increase our global counter

	       num_done++; // just finished another instruction
	       PC++;
	  }
     }

     // while loop asking for user input for next command
     public void run() {
	  // set up to read user input from console
	  BufferedReader cons = new BufferedReader(new InputStreamReader(System.in));

	  if(cons == null) {
	       System.out.println("No console available. Quitting.");
	       System.exit(1);
	  }

	  /*
	    InputStreamReader input_stream = null;
	    try { input_stream = new InputStreamReader(System.in); }
	    catch(Exception e) {
	    System.out.println("Could not get command line input. Quitting.");
	    System.exit(1);
	    }
	  */

	  String input = null;
	  StringTokenizer input_tokens = null; 
	  String curr_token;

	  while(true) {
	       System.out.print(">> "); // "command prompt"

	       try {
		    input = cons.readLine(); // get input from user
	       } catch (Exception e) {
		    System.out.println("Couldn't read input.  Bye.");
		    System.exit(1);
	       }

	       input_tokens = new StringTokenizer(input); // tokenize the input for easier parsing

	       // make sure it is a valid command and do that command
	       curr_token = input_tokens.nextToken();
	       if(curr_token.equals("iload")) {
		    if(input_tokens.countTokens() != 2) System.out.println("usage: iload $file_name $start_addr");
		    else {
			 String file_name = input_tokens.nextToken();
			 int start_addr = Integer.parseInt(input_tokens.nextToken());
			 loadMem(file_name,start_addr,true);
		    }
	       }
	       else if(curr_token.equals("dload")) {
		    if(input_tokens.countTokens() != 2) System.out.println("usage: dload $file_name $start_addr");
		    else {
			 String file_name = input_tokens.nextToken();
			 int start_addr = Integer.parseInt(input_tokens.nextToken());
			 loadMem(file_name,start_addr,false);
		    }
	       }
	       else if(curr_token.equals("go")) {
		    if(input_tokens.countTokens() != 1) System.out.println("usage: go $number");
		    else {
			 int number = Integer.parseInt(input_tokens.nextToken());
			 execute(number);
		    }
	       }
	       else if(curr_token.equals("dump_reg")) {
		    printRegFile();
	       }
	       else if(curr_token.equals("set_reg")) {
		    if(input_tokens.countTokens() != 2) System.out.println("usage: set_reg $reg_num $value");
		    else {
			 int reg_num = Integer.parseInt(input_tokens.nextToken());
			 Int34 value = new Int34(Long.parseLong(input_tokens.nextToken()));
			 setReg(reg_num,value);
		    }
	       }
	       else if(curr_token.equals("dump_imem")) {
		    if(input_tokens.countTokens() != 2) System.out.println("usage: dump_imem $start_addr $range");
		    else {
			 int start_addr = Integer.parseInt(input_tokens.nextToken());
			 int range = Integer.parseInt(input_tokens.nextToken());
			 printIMem(start_addr,range);
		    }
	       }
	       else if(curr_token.equals("set_imem")) {
		    if(input_tokens.countTokens() != 2) System.out.println("usage: set_imem $addr $value");
		    else {
			 int addr = Integer.parseInt(input_tokens.nextToken());
			 String value = input_tokens.nextToken();
			 setIMem(addr,value);
		    }
	       }
	       else if(curr_token.equals("dump_dmem")) {
		    if(input_tokens.countTokens() != 2) System.out.println("usage: dump_dmem $start_addr $range");
		    else {
			 int start_addr = Integer.parseInt(input_tokens.nextToken());
			 int range = Integer.parseInt(input_tokens.nextToken());
			 printDMem(start_addr,range);
		    }
	       }
	       else if(curr_token.equals("set_dmem")) {
		    if(input_tokens.countTokens() != 2) System.out.println("usage: set_dmem $addr $value");
		    else {
			 int addr = Integer.parseInt(input_tokens.nextToken());
			 Int34 value = new Int34(Long.parseLong(input_tokens.nextToken()));
			 setDMem(addr,value);
		    }
	       }
	       else if(curr_token.equals("dump_channel")) {
		    if(input_tokens.countTokens() != 1) System.out.println("usage: dump_channel $chan_number");
		    else {
			 int chan_num = Integer.parseInt(input_tokens.nextToken());
			 printChannel(chan_num);
		    }
	       }
	       else if(curr_token.equals("put_channel")) {
		    if(input_tokens.countTokens() != 2) System.out.println("usage: put_channel $chan_number $value");
		    else {
			 int chan_num = Integer.parseInt(input_tokens.nextToken());
			 Int34 value = new Int34(Long.parseLong(input_tokens.nextToken()));
			 addToChannel(chan_num,value);
		    }
	       }
	       else if(curr_token.equals("clear_channel")) {
		    if(input_tokens.countTokens() != 1) System.out.println("usage: clear_channel $chan_number");
		    else {
			 int chan_num = Integer.parseInt(input_tokens.nextToken());
			 clearChannel(chan_num);
		    }
	       }
	       else if(curr_token.equals("set_buf_size")) {
		    if(input_tokens.countTokens() != 1) System.out.println("usage: set_buf_size $size");
		    else {
			 int size = Integer.parseInt(input_tokens.nextToken());
			 setBufferSize(size);
		    }
	       }
	       else if(curr_token.equals("instr_count")) {
		    System.out.println(inst_count + " instructions executed so far");
	       }
	       else if(curr_token.equals("dump_pc")) {
		    System.out.println("current PC is " + PC);
	       }
	       else if(curr_token.equals("reset")) {
		    resetSimulator();
	       }
	       else if( curr_token.equals("exit")) {
		    System.out.println("leaving so soon? ... Bye!");
		    break;
	       }
	       else {
		    System.out.println("unrecognized command.");
	       }
	  }
     }
     
     // set the registers value
     public int setRegisters (String instruction, int start, int end) {
    	String t;
    	int i;
    	t = instruction.substring(start, end);
    	i = Integer.valueOf(t,2).intValue();
    	//System.out.println("setting register " + t + " to " + i);
    	return i;
    }

     public static void main(String[] args) {
	  ISASimulator sim = new ISASimulator();

	  sim.run(); // run the simulator
     }
}
